const { readdirSync, readFileSync, writeFileSync } = require("fs")
const { parse } = require("json5")
const { join } = require("path")
const { execSync } = require("child_process")
const { uniq } = require("es-toolkit")
const { dedent } = require("ts-dedent")

async function main() {
    const pkg = JSON.parse(readFileSync("../../package.json", "utf-8"))
    const dir = "../cli/genaisrc"
    const fp = "./src/default_prompts.ts"
    const fmp = "../../docs/src/content/docs/reference/scripts/system.mdx"
    const fnp = "../../docs/src/components/BuiltinTools.mdx"
    const fap = "../../docs/src/components/BuiltinAgents.mdx"
    console.debug(`bundling ${dir}/*.genai.js into default_prompts.ts`)
    const promptMap = {}
    const prompts = readdirSync(dir)
    for (const prompt of prompts) {
        if (!/\.genai\.m?ts$/.test(prompt)) continue
        const text = readFileSync(`${dir}/${prompt}`, "utf-8")
        if (/^system\./.test(prompt)) {
            const id = prompt.replace(/\.genai\.m?ts$/i, "")
            if (promptMap[id]) throw new Error(`duplicate prompt ${id}`)
            promptMap[id] = text
        }
    }
    console.log(`found ${Object.keys(promptMap).length} prompts`)
    console.debug(Object.keys(promptMap).join("\n"))
    const promptFooDriver = readFileSync(
        "./src/genaiscript-api-provider.mjs",
        "utf-8"
    )
    const logCategories = uniq([
        "script",
        "agent*",
        ...Array.from(
            execSync(
                `grep -r 'debug("genaiscript:.*")' --include \*.ts --exclude-dir='.genaiscript' .`
            )
                .toString("utf8")
                .matchAll(/debug\("(?<category>genaiscript:[^"]+)"\)/g)
        )
            .sort()
            .map((m) => m.groups.category),
    ])
    writeFileSync(
        "./src/dbg.ts",
        dedent`// auto-generated: do not edit
        export const DEBUG_CATEGORIES = ${JSON.stringify(logCategories)};\n`,
        "utf-8"
    )
    const genaiscriptdts = [
        "./src/types/prompt_template.d.ts",
        "./src/types/prompt_type.d.ts",
    ]
        .map((fn) => readFileSync(fn, { encoding: "utf-8" }))
        .map((src) =>
            src.replace(/^\/\/\/\s+<reference\s+path="[^"]+"\s*\/>\s*$/gm, "")
        )
        .join("")
        .replace("@version 0.0.0", `@version ${pkg.version}`)
    const githubCopilotInstructions = readFileSync(
        "../../.genaiscript/instructions/genaiscript.instructions.md",
        "utf-8"
    )
    const promptDefs = {
        "jsconfig.json": JSON.stringify(
            {
                compilerOptions: {
                    lib: ["ES2024"],
                    target: "ES2024",
                    module: "ES2022",
                    moduleDetection: "force",
                    checkJs: true,
                    allowJs: true,
                    skipLibCheck: true,
                },
                include: ["*.js", "./genaiscript.d.ts"],
            },
            null,
            4
        ),
        "tsconfig.json": JSON.stringify(
            {
                compilerOptions: {
                    lib: ["ES2024"],
                    target: "ES2024",
                    module: "NodeNext",
                    moduleDetection: "force",
                    moduleResolution: "nodenext",
                    checkJs: true,
                    allowJs: true,
                    skipLibCheck: true,
                    noEmit: true,
                    allowImportingTsExtensions: true,
                    verbatimModuleSyntax: true,
                    resolveJsonModule: true,
                    erasableSyntaxOnly: true,
                },
                include: ["**/*.mjs", "**/*.mts", "./genaiscript.d.ts"],
            },
            null,
            4
        ),
        "genaiscript.d.ts": genaiscriptdts,
    }

    // listing list of supported wasm languages
    const wasms = await readdirSync("../../node_modules/tree-sitter-wasms/out/")
        .map((file) => /^tree-sitter-(\w*)\.wasm$/.exec(file))
        .map((m) => m?.[1])
        .filter((f) => !!f)
    console.log(`found ${wasms.length} wasms`)

    const functions = Object.keys(promptMap)
        .sort()
        .map((k) => {
            const v = promptMap[k]
            const tools = []
            v.replace(
                /def(Agent|Tool)\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"/gm,
                (m, kind, name, description) => {
                    tools.push({
                        id: k,
                        kind: kind.toLowerCase(),
                        name,
                        description,
                    })
                    return ""
                }
            )
            return tools
        })
        .flat()
    console.log(`found ${functions.length} tools`)

    writeFileSync(
        join(dir, "genaiscript.d.ts"),
        promptDefs["genaiscript.d.ts"],
        "utf-8"
    )

    const text = `// autogenerated - node bundleprompts.mjs
export const promptDefinitions = Object.freeze<Record<string, string>>(${JSON.stringify(
        promptDefs,
        null,
        4
    )});

export const treeSitterWasms: string[] = ${JSON.stringify(wasms)};

export const githubCopilotInstructions = ${JSON.stringify(githubCopilotInstructions)}

export const promptFooDriver = ${JSON.stringify(promptFooDriver)}
\n`

    writeFileSync(fp, text, "utf-8")

    const markdown = `---
title: System Prompts
sidebar:
    order: 10
description: Learn how to utilize system prompts to enhance script execution in GenAIScript.
keywords: system prompts, script execution, genai templates, environment consistency
---
System prompts are scripts that are executed and injected before the main prompt output.

-   \`system.*.genai.js\` are considered system prompt templates
-   system prompts are unlisted by default
-   system prompts must use the \`system\` instead of \`script\`
-   system prompts are executed with the same environment as the main prompt

\`\`\`js title="system.zero_shot_cot.genai.js" "system"
system({
    title: "Zero-shot Chain of Thought",
})
export default function (ctx: ChatGenerationContext) {    
    const { $ } = ctx
    $\`Let's think step by step.\`
}
\`\`\`

:::caution

System prompts must have a default function and use the \`ctx\` passed in the function.

:::

To use system prompts in script, populate the \`system\` field with script identifiers.

\`\`\`js title="myscript.genai.js" 'system: ["system.zero_shot_cot"]'
script({
    ...,
    system: ["system.zero_shot_cot"]
})
$\`Let's think step by step.\`
\`\`\`

It is also possible to populate system script by include tool names
which will result in importing the tool into the script.

\`\`\`js
script({
    ...,
    tools: ["math_eval"]
})
\`\`\`

## Parameters and variables

System also support parameters as script but the parameter names will automatically be prepended
with the script id

- declare and use the parameter in the system script

\`\`\`js title="system.fs_read_summary.genai.js"
system({ ...,
    parameters: {
        model: {
            type: "string",
            description: "LLM model to use"
        },
    },
})
export default function (ctx: ChatGenerationContext) {    
    const { env } = ctx
    // populate from the default value or script override
    const model = env.vars["system.fs_read_summary.model"]
}
\`\`\`

- override the parameter value in the script script

\`\`\`js
script({ ...,
    system: ["system", "system.fs_read_summary"],
    vars: {
        "system.fs_read_summary.model": "ollama:phi3",
    },
})
\`\`\`

- override the parameter value in instance of the system script

\`\`\`js
script({ ...,
    system: [
        "system", 
        { 
            id: "system.fs_read_summary", 
            parameters: { model: "ollama:phi3" },            
         }],
})
\`\`\`

## Automated System Prompts

When unspecified, GenAIScript inspects the source code of the script
to determine a reasonable set of system prompts ([source code](https://github.com/microsoft/genaiscript/blob/main/packages/core/src/systems.ts)).

The default mix is

- system
- system.output_markdown
- system.explanations
- system.safety_jailbreak
- system.safety_harmful_content
- system.safety_protected_material

On top of the default, injects other system scripts based on keyword matching.

## Builtin System Prompts

GenAIScript comes with a number of system prompt that support features like creating files, extracting diffs or
generating annotations. If unspecified, GenAIScript looks for specific keywords to activate the various system prompts.

${Object.keys(promptMap)
    .sort()
    .map((k) => {
        const v = promptMap[k]
        const m = /\b(?<kind>system|script)\(\s*(?<meta>\{.*?\})\s*\)/s.exec(v)
        const meta = parse(m.groups.meta)
        const tools = []
        v.replace(
            /defTool\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"/gm,
            (m, name, description) => {
                tools.push({ name, description })
                return ""
            }
        )
        return `### \`${k}\`

${meta.title || ""}

${meta.description || ""}

${tools.map(({ name, description }) => `-  tool \`${name}\`: ${description}`).join("\n")}

\`\`\`\`\`js wrap title="${k}"
${v}
\`\`\`\`\`
`
    })
    .join("\n\n")}
`
    writeFileSync(fmp, markdown, "utf-8")

    writeFileSync(
        fnp,
        `---
title: Builtin Tools
description: List of tools in system prompts
---
import { LinkCard } from '@astrojs/starlight/components';

### Builtin tools

${functions
    .filter(({ kind }) => kind === "tool")
    .map(
        ({ id, name, description }) =>
            `<LinkCard title="${name}" description="${description}" href="/genaiscript/reference/scripts/system#${id.replace(/[^a-z0-9_]/gi, "")}" />`
    )
    .join("\n")}

`,
        "utf-8"
    )
    writeFileSync(
        fap,
        `---
title: Builtin Agents
description: List of agents in system prompts
---
import { LinkCard } from '@astrojs/starlight/components';

### Builtin Agents

${functions
    .filter(({ kind }) => kind === "agent")
    .map(
        ({ id, name, description }) =>
            `<LinkCard title="agent ${name}" description="${description}" href="/genaiscript/reference/scripts/system#${id.replace(/[^a-z0-9_]/gi, "")}" />`
    )
    .join("\n")}
`,
        "utf-8"
    )
}
main()
